from machine import Pin, PWM
from utime import sleep_ms


class MIDI:
	_OCTAVES = 'ABCDEFG#$X'

	_NORMAL_TONE = {
		'A1': 55, 'B1': 62, 'C1': 33, 'D1': 37, 'E1': 41, 'F1': 44, 'G1': 49,

		'A2': 110, 'B2': 123, 'C2': 65, 'D2': 73, 'E2': 82, 'F2': 87, 'G2': 98,

		'A3': 220, 'B3': 247, 'C3': 131, 'D3': 147, 'E3': 165, 'F3': 175, 'G3': 196,

		'A4': 440, 'B4': 494, 'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349, 'G4': 392,

		'A5': 880, 'B5': 988, 'C5': 523, 'D5': 587, 'E5': 659, 'F5': 698, 'G5': 784,

		'A6': 1760, 'B6': 1976, 'C6': 1047, 'D6': 1175, 'E6': 1319, 'F6': 1397, 'G6': 1568,

		'A7': 3520, 'B7': 3951, 'C7': 2093, 'D7': 2349, 'E7': 2637, 'F7': 2794, 'G7': 3135,

		'A8': 7040, 'B8': 7902, 'C8': 4186, 'D8': 4699, 'E8': 5274, 'F8': 5588, 'G8': 6271,

		'A9': 14080, 'B9': 15804
	}

	_RISING_TONE = {
		'A1': 58, 'C1': 35, 'D1': 39, 'F1': 46, 'G1': 52,

		'A2': 117, 'C2': 69, 'D2': 78, 'F2': 93, 'G2': 104,

		'A3': 233, 'C3': 139, 'D3': 156, 'F3': 185, 'G3': 208,

		'A4': 466, 'C4': 277, 'D4': 311, 'F4': 370, 'G4': 415,

		'A5': 932, 'C5': 554, 'D5': 622, 'F5': 740, 'G5': 831,

		'A6': 1865, 'C6': 1109, 'D6': 1245, 'F6': 1480, 'G6': 1661,

		'A7': 3729, 'C7': 2217, 'D7': 2489, 'F7': 2960, 'G7': 3322,

		'A8': 7459, 'C8': 4435, 'D8': 4978, 'F8': 5920, 'G8': 6645,

		'A9': 14917
	}

	_FALLING_TONE = {
		'B1': 58, 'D1': 35, 'E1': 39, 'G1': 46, 'A1': 52,

		'B2': 117, 'D2': 69, 'E2': 78, 'G2': 93, 'A2': 104,

		'B3': 233, 'D3': 139, 'E3': 156, 'G3': 185, 'A3': 208,

		'B4': 466, 'D4': 277, 'E4': 311, 'G4': 370, 'A4': 415,

		'B5': 932, 'D5': 554, 'E5': 622, 'G5': 740, 'A5': 831,

		'B6': 1865, 'D6': 1109, 'E6': 1245, 'G6': 1480, 'A6': 1661,

		'B7': 3729, 'D7': 2217, 'E7': 2489, 'G7': 2960, 'A7': 3322,

		'B8': 7459, 'D8': 4435, 'E8': 4978, 'G8': 5920, 'A8': 6645,

		'B9': 14917
	}

	def __init__(self):
		self._ticks = 4
		self._bpm = 120
		self._beat = 60000 / self._bpm / self._ticks
		self._octave = 4
		self._duration = 4
		self._pin = Pin(5)

	def init(self, pin):
		self._pin = Pin(pin)

	def set_tempo(self, ticks=4, bpm=120):
		self._ticks = ticks
		self._bpm = bpm
		self._beat = 60000 / self._bpm / self._ticks

	def set_octave(self, octave=4):
		self._octave = octave

	def set_duration(self, duration=4):
		self._duration = duration

	def parse_tone(self, tone):
		if tone.find('#') != -1:
			tone = tone.replace('#', '')
			dictionary = self._RISING_TONE
		elif tone.find('$') != -1:
			tone = tone.replace('$', '')
			dictionary = self._FALLING_TONE
		else:
			dictionary = self._NORMAL_TONE

		sep_tone = tone.split(":")
		sep_length = len(sep_tone)

		# freq = 1
		time = self._beat * self._duration
		continued = False

		tone_size = len(sep_tone[0])

		if 'X' in tone:
			freq = 10
		elif tone_size == 1:
			freq = dictionary[sep_tone[0] + str(self._octave)]
		else:
			freq = dictionary[sep_tone[0]]
			self.set_octave(sep_tone[0][1])

		if sep_length > 1:
			time = self._beat * int(sep_tone[1])
			self.set_duration(int(sep_tone[1]))

		if sep_length == 3:
			continued = True

		return int(freq), int(time), continued

	def play(self, tune):
		pwm = PWM(self._pin)

		try:
			for tone in tune:
				tone = tone.upper()

				if tone[0] not in self._OCTAVES:
					continue

				freq, duty, continued = self.parse_tone(tone)

				pwm.freq(freq)
				# print("====duty:", duty if duty <= 1023 else 500)
				if 'X' in tone:
					pwm.duty(0)
				else:
					pwm.duty(duty if duty <= 1023 else 1000)

				sleep_ms(duty)

				if not continued:
					pwm.duty(0)
					sleep_ms(5)
		finally:
			pwm.deinit()
